package org.barad.architecture.analyzer.archpad.event.listeners;

import java.awt.Component;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.EventListener;

import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;

import org.barad.architecture.analyzer.archpad.ArchPad;
import org.barad.architecture.analyzer.archpad.CustomizedJGraph;

/**
 * This class shows a context menu that offers all
 * available actions in the current context. This
 * class holds a list of the default {@link MouseListener}s
 * of JGrap and delegates to them in case it does not
 * consume the {@link MouseEvent}. This is required since
 * Swing does not give guarantees in the order of notifying
 * the {@link EventListener}s.
 * 
 * @author svetoslavganov@gmail.com (Svetoslav R. Ganov)
 *
 */
public class JGraphContextMenuMouseListener implements MouseListener {
	private ArchPad archPad;
	private JPopupMenu jPopupMenu;
	private MouseListener[] mouseListeners;
	private JMenuItem saveComponentAs;
	private JMenuItem groupAsComponentItem;
	private JMenuItem ungroupComponentItem;
	private JMenuItem colapseComponentItem;
	private JMenuItem expandComponentItem;
	private JMenuItem createRegionItem;

	/**
	 * Creates a new instance.
	 * 
	 * @param archPad The {@link ArchPad} instance.
	 */
	public JGraphContextMenuMouseListener(ArchPad archPad) {
		this.archPad = archPad;
	}
	
	/**
	 * Delegates to the rest JGraph {@link MouseListener}s.
	 * 
	 * @param The {@link MouseEvent}.
	 */
	public void mouseClicked(MouseEvent e) {
		if (mouseListeners != null) {
			for(MouseListener ml: mouseListeners) {
				ml.mouseClicked(cloneMouseEvent(e));
			}
		}
	}

	/**
	 * Delegates to the rest JGraph {@link MouseListener}s.
	 * 
	 * @param The {@link MouseEvent}.
	 */
	public void mouseEntered(MouseEvent e) {
		if (mouseListeners != null) {
			for(MouseListener ml: mouseListeners) {
				ml.mouseEntered(cloneMouseEvent(e));
			}
		}
	}

	/**
	 * Delegates to the rest JGraph {@link MouseListener}s.
	 * 
	 * @param The {@link MouseEvent}.
	 */
	public void mouseExited(MouseEvent e) {
		if (mouseListeners != null) {
			for(MouseListener ml: mouseListeners) {
				ml.mouseExited(cloneMouseEvent(e));
			}
		}
	}

	/**
	 * Intercepts the mouse right click event upon which
	 * a context menu is shown, otherwise delegates to the 
	 * rest JGraph {@link MouseListener}s.
	 * 
	 * @param The {@link MouseEvent}.
	 */
	public void mousePressed(MouseEvent e) {
		if (e.getButton() == MouseEvent.BUTTON3 && e.getClickCount() == 1) {
			showJGraphContextMenu(e.getPoint());
		} else {
			if (mouseListeners != null) {
				for(MouseListener ml: mouseListeners) {
					ml.mousePressed(cloneMouseEvent(e));
				}
			}
		}
	}

	/**
	 * Delegates to the rest JGraph {@link MouseListener}s.
	 * 
	 * @param The {@link MouseEvent}.
	 */
	public void mouseReleased(MouseEvent e) {
		if (mouseListeners != null) {
			for(MouseListener ml: mouseListeners) {
				ml.mouseReleased(cloneMouseEvent(e));
			}
		}
	}
	
	/**
	 * Gets the array of JGraph's {@link MouseEvent}s.
	 * 
	 * @return The array of {@link MouseListener}.
	 */
	public MouseListener[] getMouseListeners() {
		return mouseListeners;
	}

	/**
	 * Sets the array of JGraph's {@link MouseEvent}s.
	 * 
	 * @param mouseListeners The array of {@link MouseListener}.
	 */
	public void setMouseListeners(MouseListener[] mouseListeners) {
		this.mouseListeners = mouseListeners;
	}
	
	/**
	 * Sets the array of JGraph's {@link MouseEvent}s given
	 * the component in which they are registered for events.
	 * 
	 * @param mouseListeners The array of {@link MouseListener}.
	 */
	public void setMouseListeners(Component component) {
		mouseListeners = component.getMouseListeners();
		for (MouseListener ml: mouseListeners) {
			component.removeMouseListener(ml);
		}
	}
	
	/**
	 * Shows the context menu. If the menu still does not exist
	 * one is created.
	 * 
	 * @param point The {@link Point} at which to shoe the menu.
	 */
	private void showJGraphContextMenu(Point point) {
		if (jPopupMenu == null) {
			createPopUpMenu();
		}
		setEnabledMenuItems();
		jPopupMenu.show(archPad.getCustomizedJGraph(), (int)point.getX(), (int)point.getY());
	}

	/**
	 * Sets the enabled options, i.e. functions in the context menu
	 * according to the current context.
	 */
	private void setEnabledMenuItems() {
		CustomizedJGraph jGraph = archPad.getCustomizedJGraph();
		this.groupAsComponentItem.setEnabled(jGraph.isGroupComponent());
		this.ungroupComponentItem.setEnabled(jGraph.isUngroupComponent());
		this.saveComponentAs.setEnabled(jGraph.isSaveComponent());
		this.expandComponentItem.setEnabled(jGraph.isExpandComponent());
		this.colapseComponentItem.setEnabled(jGraph.isCollapseComponent());
		this.createRegionItem.setEnabled(jGraph.isCreateRegion());
		//push this to ArchPad
		archPad.setEnabledToolBarWidgets();
	}
	
	/**
	 * Creates the context pop up menu.
	 */
	private void createPopUpMenu() {
		jPopupMenu = new JPopupMenu();
		//save as template component
		saveComponentAs = new JMenuItem("Save composition");
		saveComponentAs.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.ALT_MASK));
		saveComponentAs.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				archPad.getActions().saveComponentAs();
			}
		});
		jPopupMenu.add(saveComponentAs);
		jPopupMenu.addSeparator();
		//group as component
		groupAsComponentItem = new JMenuItem("Group as composition");
		groupAsComponentItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_G, ActionEvent.ALT_MASK));
		groupAsComponentItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				archPad.getComponentManager().groupAsComponent();
			}
		});
		jPopupMenu.add(groupAsComponentItem);
		//ungroup component(s)
		ungroupComponentItem = new JMenuItem("Unroup compositions(s)");
		ungroupComponentItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_U, ActionEvent.ALT_MASK));
		ungroupComponentItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				archPad.getComponentManager().ungroupSelectedComponents();
			}
		});
		jPopupMenu.add(ungroupComponentItem);
		jPopupMenu.addSeparator();
		//expand component(s)
		expandComponentItem = new JMenuItem("Expand composition(s)");
		expandComponentItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, ActionEvent.ALT_MASK));
		expandComponentItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				archPad.getComponentManager().expandComponent();
			}
		});
		jPopupMenu.add(expandComponentItem);
		//collapse component(s)
		colapseComponentItem = new JMenuItem("Colapse composition(s)");
		colapseComponentItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.ALT_MASK));
		colapseComponentItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				archPad.getComponentManager().collapseComponent();
			}
		});	
		jPopupMenu.add(colapseComponentItem);
		jPopupMenu.addSeparator();
		//create region
		createRegionItem = new JMenuItem("Create region");
		createRegionItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, ActionEvent.ALT_MASK));
		createRegionItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				archPad.getRegionManager().createRegion();
			}
		});	
		jPopupMenu.add(createRegionItem);
	}
	
	/**
	 * Clones a mouse event.
	 * 
	 * @param e The {@link MouseEvent}.
	 * @return The clone.
	 */
	private MouseEvent cloneMouseEvent(MouseEvent e) {
		MouseEvent clone = new MouseEvent(e.getComponent(), e.getID(), e.getWhen(), 
				e.getModifiers(), e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger());
		return clone;
	}
}
